<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="app">
        {{age}}
        <h2>{{msg}}</h2>
        <h1 id="hh">{{msg}}</h1>
    </div>
</body>
</html>
<script>
    class Dep{
        constructor(){
            this.subs=[];//用于存订阅者
        }
        addSub(watcher){  //添加订阅者
            this.subs.push(watcher)
        }
        notify(){ //通知（发布）视图需要更新
            this.subs.forEach(watcher=>{
                watcher.update() //调用watcher的更新方法
            })
        }
    }

    //watcher (订阅者)类  这个类用与添加订阅者
    class Watcher{ //当页面通过插值或者动态参数访问了DATA中的属性，那就相当于订阅了该属性，这个属性更改了，所有和这个属性相关的逻辑都要更新
            constructor(data,key,fn){//data是订阅的对象 key是订阅的属性  fn是订阅的目的（当被通知以后，做出的行为）
                Dep.target=this;//让Dep的target属性指向当前的订阅者
                this.data=data;
                this.key=key;
                this.fn=fn;
                this.value=data[key];
                Dep.target=null;
            }
            update(){//更新
                this.value=this.data[this.key];//重新触发当前的属性get，获取最新的更新的值
                this.fn(this.value)
            }
    }

//通过发布订阅模式结合Object.defineProperty实现mvvm数据劫持以及响应式的编程

//声明一个vue的类

class Vue{
    constructor(options){
        this.$data =options.data;//添加$data属性
        this.$el=document.querySelector(options.el) //获取传入dom元素
        // ||document.getElementById(options.el.substring(1,options.el.length) )

        //属性代理。将$data中的数据，代理到当前的跟实例
        this.proxy_data()
        observer(this.$data) //添加发布者，劫持data中所有的属性
        this.compile()
    }
    compile() {//编译模板
            //获取页面上的el元素,把它放到内存中
            let fragment = document.createDocumentFragment();//创建一个碎片容器
            Array.prototype.slice.call(this.$el.childNodes).forEach(child => {
                // console.log(child);
                fragment.appendChild(child);//将所有的子节点添加到fragment容器
            })

            // console.log(fragment.childNodes);

            //遍历fragment,对初始数据进行编译
            Array.from(fragment.childNodes).forEach(node => {
                // console.log(node);  
                //获取节点的内容
                let text = node.textContent; //获取节点中的内容
                let regExp = /\{\{([^\{\{\}\}]+)\}\}/
                // console.log(text);
                let match = null; //储存匹配好的属性
                let prop;//取出空白换行之后的属性
                // console.log(regExp.exec(text)
                match = regExp.exec(text)
                // console.log(prop);
                if (match) {
                    prop = match[1]
                    // console.log(prop);
                    // console.log(node);
                    // console.log(node.textContent);
                    node.textContent = this[prop];//将节点中的插值替换成数据内容
                    // console.log(this.$data);
                    // console.log(prop);
                    new Watcher(this.$data, prop, function (value) {
                        // console.log(value);
                        node.textContent = value;
                    })
                }

            })


            //把fragment添加到页面中

            this.$el.appendChild(fragment)
        }


    // compile(){  //编译模板
    //     //获取页面上EL元素，把它放到内存中
    //     let fragment =document.createDocumentFragment();//创建一个碎片容器
    //     Array.prototype.slice.call(this.$el.$childNodes).forEach(child=>{
    //         fragment.appendChild(child);//将所有的子节点添加到fragment容器
    //     })

    //     //遍历Fragment,对初始数据进行编译
    //     Array.from(fragment.childNodes).forEach(node=>{
    //         //获取节点的内容
    //         let text=node.textContent;//获取节点中的内容
    //         let regEXP=/\{\{([^\{\{\}\}]+)\}\}/
    //         let match = null //存储匹配好的属性
    //         let prop //取出空白换行之后的属性
    //         match =regEXP.exec(Text)
    //         if(match){
    //             prop=match[1];
    //             node.textContent=this[prop];//将节点中的插值替换成数据内容
    //             new Watcher(this.$data,prop,function(value){
    //                 node.textContent =value
    //             })
    //         }
    //     })
    //     //把 fragment 添加到页面中
    //     this.$el.appendChild(fragment)
    // }
    proxy_data(){
        Object.keys(this.$data).forEach(key=>{
            Object.defineProperty(this,key,{
                configurable:true,
                enumerable:true,
                get(){
                    return this.$data[key]
                },
                set(newVal){
                    if(newVal!==this.$data[key]){
                        this.$data[key]=newVal   //值不相同的时候才触发被劫持的$data中的属性的set
                    }
                }

            })
        })


    }
   
    
}
const vm=new Vue({
    el:"#app",
    data: {
        name:"发发",
        age:30,
        msg:"花王",
        person:{
            name:"李蕾"
        }

    }
})
//劫持data中的属性
function observer(data){
    if(typeof data === "object"){
        Object.keys(data).forEach(key=>{
            defineReactive(data,key,data[key])
        })
    }
}
//defineReactive函数
function defineReactive(data,key,value){ //数据劫持的核心方法
    observer(value)
    let dep=new Dep()//每个属性都要通过一个实例化的Dep来解耦合
    Object.defineProperty(data,key,{
        configurable:true, //是否可以配置删除这个属性
        enumerable:true, //是否可枚举
        get(){

            Dep.target&&dep.addSub(Dep.target)
            return value
        },
        set(newVal){
            value =newVal
            dep.notify()
        }
    })

}
</script>